#!/bin/env python2.5.1

import Dingo.API as api
import random
import time
import copy
from sets import Set

#Static variables
sink = "Sink[-1]"   #Sink node ID
WTime = 10          #Back-off waiting time
ss_slot_time = 0.2  #

class sinkRouting:
    def __init__(self, sensor, NUMSENSOR):
        self.sensor = sensor
        self.NUMSENSOR = NUMSENSOR
        self.CH = []    #List to store CH nodes of the 1st protocol round

    def StartUp(self):  #Initialize the protocol
        #sensor = self.sensor
        NUMSENSOR = self.NUMSENSOR
        if self.sensor.id == "Sink[-1]" and self.sensor.state == "Sink":
            self.sensor.state = "DSink" #Change state

            #Calculate optimal number of CH nodes
            OptNumCH = int((5*NUMSENSOR)/100)
            if OptNumCH < 1:
                OptNumCH = 2
	        self.sensor.debug("SIM: Number of nodes is: %d"  % NUMSENSOR)
            self.sensor.debug("SIM: Optimal number of CH nodes is: %d" % OptNumCH)
            self.sensor.debug("SIM: Sink starts network discovery ... ... ...\n")

            #Select CH nodes
            for i in range(OptNumCH):
                r = random.randrange(NUMSENSOR)
                temp = "Sensor[%d]" %r
                while temp in self.CH:
                    r = random.randrange(NUMSENSOR)
                    temp = "Sensor[%d]" %r
                self.CH.append(temp)

            self.sensor.debug("SIM: Selected CH nodes are:\n")
            print self.CH
            self.sensor.debug("SIM: --------------------------------------------\n")

            #Flood the network
            self.sensor.debug("RADIO: %s \t send    DISC" % self.sensor.id)
            self.packet = APacket(self.sensor.id, None, ["DISC", self.CH])
            ### Put packet on send queue
            self.sensor.send_q.put(self.packet) 
            ### Send next packet in queue with arc colour
            self.sensor.send(api.SensorColor.FromName("Red"))

    def receiveTask(self,themsg):
        self.themsg = themsg
        mType = self.themsg.msg[0]  #Type of message received

        if mType == "TO_SINK":
            self.sensor.points.update(self.themsg.msg[1])
            #self.sensor.points = list(sets.Set(self.sensor.points)) #Remove duplicates ONLY work for high map data
            self.sensor.debug("SIM: The received data to time %s is:\n %s" % (timeStamp(), self.sensor.points))



class sensorRouting:
    def __init__(self, sensor):
        self.sensor = sensor

        #Local variables
        self.NumHop = 1000      #Initially infinite number of hops
        self.tcolor = "Green"   #Temp color cell
        self.Wtimestamp = timeStamp()   #Start back-off waiting time
        self.seqNum = self.sensor.number + 10  #Query unique message Id

    def receiveTask(self,themsg):
        self.themsg = themsg

        mType = self.themsg.msg[0]  #Type of message received
        #node = self.sensor.node     #Graphical sensor

        #-----Receive broadcast message DISC
        if self.themsg.receiver_id == None and mType == "DISC" and self.sensor.state == "Waiting":

            CHList = self.themsg.msg[1]     #Copy the list of CH nodes
            self.sensor.state = "Actuator"  #Change state
            self.sensor.setColour(api.SensorColor.FromName("Green")) #Change colour
            self.sensor.s_Path = self.themsg.sender_id #Set path to the sink

            #When node is selected as a CH
            if self.sensor.id in CHList:

                self.sensor.setColour(api.SensorColor.FromName("Blue")) #Blue for CH nodes
                self.sensor.isCH = True

                #Forward received message
                self.sensor.debug("RADIO: %s    forward    DISC" % self.sensor.id)
                self.packet = APacket(self.sensor.id, None, self.themsg.msg[:])
                self.broadcastPacket(self.packet, "Red")

                #Wait untill all nodes are discovered
                api.Sleep(10)

                self.sensor.scheduleTime = timeStamp()  #Set TDMA creation time
                self.sensor.timestamp = timeStamp() #Set cluster start time

                #Broadcast an advertisment message
                self.startCluster(api.SensorColour.FromRGBCombined(random.randrange(1<<24)))

            else: #If receiving node was not selected as CH then forward DISC message to neighbours
                self.sensor.debug("RADIO: %s    forward    DISC" % self.sensor.id)
                self.packet = APacket(self.sensor.id, None, self.themsg.msg[:])
                self.broadcastPacket(self.packet, "Red")

        #-----Receive broadcast message ADV_CH and node state is Actuator
        elif self.themsg.receiver_id == None and not self.sensor.isCH and mType == "ADV_CH" and self.sensor.state == "Actuator" and self.sensor.AcceptADV_CH:

            #If this is the first time the node see and advertisment
            if self.sensor.myCH == '-':
                self.tcolor = self.themsg.msg[2]    #Copy cluster colour
                self.NumHop = self.themsg.msg[3]    #Copy the number of hops
                self.sensor.myCH = self.themsg.msg[1]   #Copy ID of advertising CH
                self.sensor.lastNode = self.themsg.sender_id    #Copy ID of next node to CH
                self.Wtimestamp = timeStamp()       #Start back-off timer

            #If node receives an advertisment from closer CH
            elif self.NumHop > self.themsg.msg[3]: 
                self.tcolor = self.themsg.msg[2]    #Update cluster colour
                self.NumHop = self.themsg.msg[3]    #Update the number of hops
                self.sensor.myCH = self.themsg.msg[1] #Update current CH
                self.sensor.lastNode = self.themsg.sender_id    #Update Id of next node to CH

            #Check if back-off time ended or not
            self.CheckTime()

        #-----When a node receives a join request message
        elif self.themsg.receiver_id == self.sensor.id and mType == "JOIN_REQ" and self.sensor.state == "Actuator":

            self.tempPath = []
            self.tempPath = self.themsg.msg[2]  #Path to the message creator
            self.origin = self.tempPath[0]  #The message creator

            #When CH receive the JOIN_REQ message
            if self.sensor.isCH:

                #If not already registered as member
                if self.origin not in self.sensor.members:
                    self.sensor.members.append(self.origin)    #Copy sender to cluster members list

                val = self.themsg.msg[1]        #Copy Wish value
                if self.sensor.backupV < val:   #Compare with best received value so far
                    self.sensor.backupCH = self.origin
                    self.sensor.path = self.tempPath #Copy the path to backup node

            else: #When a sensing node in the path receive a JOIN_REQ message
                self.tempPath.append(self.sensor.id) #Add itself to path
                self.new_msg = []
                self.new_msg = [self.themsg.msg[0],self.themsg.msg[1], self.tempPath] #Create new message

                self.sensor.debug("SIM: %s    send    JOIN_REQ" % self.sensor.id)
                self.packet = APacket(self.sensor.id, self.sensor.lastNode, self.new_msg)
                self.sendPacket(self.packet, "LightBlue")

        #-----When a node receives a query message ["Q",self.seqNum,self.sensor.id]
        elif self.themsg.receiver_id == None and mType == "QUERY" and self.sensor.state == "Actuator":
            if self.themsg.msg[2] != self.sensor.id and self.themsg.msg[1] not in self.sensor.cQ:
                self.sensor.cQ.append(self.themsg.msg[1])   #Mark as seen message
                self.sensor.q_Path = self.themsg.sender_id  #Remember next hop to querying node
                self.sensor.lastQ = self.themsg.msg[2]      #Remember the Id of the querying node

                #Forward received message
                self.sensor.debug("RADIO: %s    forward    QUERY" % self.sensor.id)
                self.packet = APacket(self.sensor.id, None, self.themsg.msg[:])
                self.broadcastPacket(self.packet, "Red")
                api.Sleep(0.3)
                self.answerQuery()

        #-----Deals with query answer data
        elif self.themsg.receiver_id == self.sensor.id and mType == "ANSWER" and self.sensor.state == "Actuator":
            if self.themsg.msg[2] == self.sensor.id:    #If message arrives at querying node then copy data
                self.sensor.points.update(self.themsg.msg[1])
            else:
                self.sensor.debug("RADIO: %s    send    ANSWER" % self.sensor.id)
                self.packet = APacket(self.sensor.id, self.sensor.q_Path, self.themsg.msg[:])
                self.sendPacket(self.packet, "Black")

        #-----Deals with data transmission sechdule advertisment
        elif mType == "ADV_SCH" and not self.sensor.isCH and self.themsg.msg[1] == self.sensor.myCH and self.themsg.msg[2] not in self.sensor.seenADV_SCH and self.sensor.state == "Actuator":
            self.sensor.seenADV_SCH.append(self.themsg.msg[2])
            self.order = self.themsg.msg[3]

            self.sensor.debug("RADIO: %s    forward    ADV_SCH" % self.sensor.id)
            self.packet = APacket(self.sensor.id, None, self.themsg.msg[:])
            self.broadcastPacket(self.packet, "Black")

            if str(self.sensor.id) in self.order:
                self.ind = self.order.index(self.sensor.id)
                self.xmitat = self.ind * ss_slot_time
                self.sensor.debug("SIM: %s scheduled to transmit after %s.  It is now %s" % (self.sensor.id, self.xmitat, timeStamp()))
                api.Sleep(self.xmitat)
                self.sendData()
            else:
                self.sensor.debug("SIM: Warning!!!!  %s does not have a transmit time!" % self.sensor.id)
                self.sensor.debug("SIM: Must transmit data directly to Sink.")
                self.sensor.debug("RADIO: %s    send    TO_SINK" % self.sensor.id)
                d = {(self.sensor.xc, self.sensor.yc): (self.sensor.xc, self.sensor.yc, self.sensor.data)}
                self.packet = APacket(self.sensor.id, self.sensor.s_Path, ["TO_SINK", d])
                self.sendPacket(self.packet, "Yellow")

        #Handle sending of data to CH then to sink
        elif self.themsg.receiver_id == self.sensor.id and mType == "DATA" and self.sensor.state == "Actuator":
            self.sensor.debug("RADIO: %s received a DATA message at %s" % (self.sensor.id, timeStamp()))

            #When a CH node receives data message
            if self.themsg.msg[2] == self.sensor.id and self.sensor.isCH:
                if len(self.sensor.AGdata) == 0:
                    self.sensor.AggTimeStamp = timeStamp()

                self.sensor.AGdata.update(self.themsg.msg[1])

                if self.sensor.AggTimeStamp.diff(timeStamp()) >= 10 or len(self.sensor.AGdata) == len(self.sensor.points):
                    self.dataToSink()
                    self.sensor.AGdata = {}
                    self.sensor.AggTimeStamp = timeStamp()

            #Sensing node perform data aggregation and forward aggregated data
            else:
                if len(self.sensor.AGdata) == 0:
                    self.sensor.AggTimeStamp = timeStamp()
                self.sensor.AGdata.update(self.themsg.msg[1])

                if self.sensor.AggTimeStamp.diff(timeStamp()) >= 0:
                    if len(self.sensor.AGdata) > 0:
                        if self.sensor.lastNode == '-':
                            self.dataToSink()
                            self.sensor.AGdata = {}
                            self.sensor.AggTimeStamp = timeStamp()
                        else:
                            self.sensor.debug("RADIO: %s    forward    DATA" % self.sensor.id)
                            self.packet = APacket(self.sensor.id, self.sensor.lastNode, self.themsg.msg[:])
                            self.sendPacket(self.packet, "Black")
                            self.sensor.AGdata = {}
                            self.sensor.AggTimeStamp = timeStamp()

        #Deal with the notification message sent by current CH to hand CH role to backup CH
        elif self.themsg.receiver_id == self.sensor.id and mType == "NOT" and self.sensor.state == "Actuator":

            self.new_path = self.themsg.msg[1] #Where the path is stored in the message

            if len(self.new_path) > 0: #If not the destination, then forward to next hop
                nextHop = self.new_path.pop() #Extract next hop in the path
                self.sensor.debug("RADIO: %s    forward    NOT" % self.sensor.id)
                self.packet = APacket(self.sensor.id, nextHop, [self.themsg.msg[0], self.new_path])
                self.sendPacket(self.packet, "Magenta")

            else: #If this is the destination node then:
                #node = self.sensor.node #Graphical sensor
                self.sensor.setColour("Blue") #Red for CH nodes
                self.sensor.debug("SIM: At %s node %s became a CH" % (timeStamp(), self.sensor.id))
                self.sensor.AGdata = {}
                self.sensor.isCH = True
                self.sensor.timestamp = timeStamp()
                self.startCluster(api.SensorColour.FromRGBCombined(random.randrange(1<<24))) #Broadcast advertisment message

        #-----Deal with data messages sent by CH to sink
        elif self.themsg.receiver_id == self.sensor.id and mType == "TO_SINK" and self.sensor.state == "Actuator":
            if self.sensor.id == "Sink[-1]" and self.sensor.state == "DSink":
                self.sensor.points.update(self.themsg.msg[1])
            else:
                self.sensor.debug("RADIO: %s    forward    TO_SINK" % self.sensor.id)
                self.packet = APacket(self.sensor.id, self.sensor.s_Path, self.themsg.msg[:])
                self.sendPacket(self.packet, "Yellow")

    def startCluster(self, colour):
        self.colour = colour
        self.sensor.checktdma = True    #After advertisment start checking
        self.sensor.scheduleTime = timeStamp()  #Start timer

        self.sensor.debug("RADIO: %s    send    ADV_CH" % self.sensor.id)
        self.packet = APacket(self.sensor.id, None, ["ADV_CH", self.sensor.id, self.colour, 0])
        self.broadcastPacket(self.packet, "Blue")

    def createTDMA(self):
        self.sensor.tdma = False
        if len(self.sensor.members) == 0:
            self.sensor.debug("SIM: Warning!  There are no nodes in this cluster %s!" % self.sensor.id)
        else:
            #Set the TDMA schedule and send it to all nodes in the cluster
            self.TDMAschedule = self.sensor.members
            self.sensor.scheduleTime = timeStamp()
            self.sequnceNumber = int(random.uniform(101,150))

            self.sensor.debug("SIM: %s    send    TDMA schedule: %s" % (self.sensor.id, self.sensor.members))
            self.packet = APacket(self.sensor.id, None, ["ADV_SCH", self.sensor.id, self.sequnceNumber, self.TDMAschedule])
            self.broadcastPacket(self.packet, "Blue")

    def reply(self):
        #Calculate wish value by equation latter
        wishValue = random.randrange(50)
        self.load = []
        self.load.append(self.sensor.id)

        self.sensor.debug("RADIO: %s    send    JOIN_REQ" % self.sensor.id)
        self.packet = APacket(self.sensor.id, self.sensor.lastNode, ["JOIN_REQ", wishValue, self.load])
        self.sendPacket(self.packet, "LightBlue")

    def dataToSink(self):
        self.sensor.debug("RADIO: %s    send    TO_SINK" % self.sensor.id)
        self.packet = APacket(self.sensor.id, self.sensor.s_Path, ["TO_SINK", self.sensor.AGdata])
        self.sendPacket(self.packet, "Yellow")

    def Notify(self):
        if len(self.sensor.path) > 0:
            rec = self.sensor.path.pop()
            self.sensor.debug("SIM: Notify node %s to become CH" % self.sensor.id)

            self.sensor.debug("RADIO: %s    send    NOT" % self.sensor.id)
            self.packet = APacket(self.sensor.id, rec, ["NOT", self.sensor.path])
            self.sendPacket(self.packet, "Magenta")

    def sendData(self): #Create data packet, each node use its address as message load
        #dat is as follows: (x, y): (x, y, z)
        d = (self.sensor.xc, self.sensor.yc, self.sensor.data)

        if self.sensor.isCH:
            self.sensor.points[self.sensor.xc, self.sensor.yc] = d
        else:
            self.sensor.debug("RADIO: %s    send    DATA" % self.sensor.id)
            self.packet = APacket(self.sensor.id, self.sensor.lastNode, ["DATA", {(self.sensor.xc, self.sensor.yc): (self.sensor.xc, self.sensor.yc, self.sensor.data)}, self.sensor.myCH])
            self.sendPacket(self.packet, "Black")

    def sendQuery(self): #Generate a query
        self.sensor.points = {}
        self.packet = APacket(self.sensor.id, None, ["QUERY",self.seqNum,self.sensor.id])
        self.seqNum+=1
        self.sensor.debug("RADIO: %s    send    QUERY" % self.sensor.id)
        self.broadcastPacket(self.packet, "Red")

    def answerQuery(self): #Answer a query
        self.sensor.debug("RADIO: %s    send    ANSWER" % self.sensor.id)
        d = {(self.sensor.xc, self.sensor.yc): (self.sensor.xc, self.sensor.yc, self.sensor.data)}
        self.packet = APacket(self.sensor.id, self.sensor.q_Path, ["ANSWER", d, self.sensor.lastQ])
        self.sendPacket(self.packet, "Black")

    def emptyAGdata(self):
        if self.sensor.isCH:
            self.dataToSink()
        else: #Make it ready for sensor node aggregation (maybe needed in the future)
            self.sensor.debug("RADIO: %s    send    DATA" % self.sensor.id)
            self.packet = APacket(self.sensor.id, self.sensor.lastNode, self.themsg.msg[:])
            self.sendPacket(self.packet, "Black")

        self.sensor.AGdata = {}
        self.sensor.AggTimeStamp = timeStamp()

    def sendPacket(self, packet, colour):
        self.colour = colour
        self.sensor.send_q.put(self.packet)
        self.sensor.send(api.SensorColor.FromName(self.colour))

    def broadcastPacket(self, packet, colour):
        self.colour = colour 
        ### Put packet on send queue 
        self.sensor.send_q.put(self.packet)
        ### Send next packet in queue with arc colour
        self.sensor.send(api.SensorColor.FromName(self.colour))

    
    #Method to join a CH after the back-off time ends
    def CheckTime(self):
        if self.Wtimestamp.diff(timeStamp()) >= WTime:
            #node = self.sensor.node
            print "tcolor=%s" % self.tcolor
            self.sensor.AcceptADV_CH = False  #Start dropping received advertisment messages
            self.sensor.setColour(self.tcolor) #Set colour
            self.sensor.timestamp = timeStamp() #Set start time of cluster round
            self.num = self.NumHop + 1      #Increment number of hops

            self.reply()    #Send join request

            #Forward advertisment to neighbours
            self.sensor.debug("RADIO: %s    send    ADV_CH" % self.sensor.id)
            self.packet = APacket(self.sensor.id, None, ["ADV_CH", self.sensor.myCH, self.tcolor, self.num])
            self.broadcastPacket(self.packet, "Blue")

    def checkExpires(self):

        if self.sensor.isCH: #Cluster head node
            if len(self.sensor.path) == 0:
                self.sensor.timestamp = timeStamp()
                self.sensor.debug("SIM: %s will be CH for another round" % self.sensor.id)
                self.startCluster(api.SensorColour.FromRGBCombined(random.randrange(1<<24)))
            else:
                self.sensor.debug("SIM: %s cluster round ended" % self.sensor.id)
                self.sensor.debug("SIM: Notify backup CH node ... ... ...")

                #Make sure that there is no data to send to sink before expire
                if len(self.sensor.AGdata) > 0:
                    self.dataToSink()
                    self.sensor.AGdata = {}
                    api.Sleep(0)

		    self.Notify()
		    self.sensor.isCH = False
		    self.sensor.members = []
		    self.sensor.points = {}
		    self.sensor.AggTimeStamp = timeStamp()
		    self.sensor.scheduleTime = timeStamp()
		    self.sensor.timestamp = timeStamp()
		    self.sensor.myCH = '-'
		    self.lastNode = '-'
		    self.sensor.backupV = 0   
		    self.sensor.path = []
		    self.Wtimestamp = timeStamp()
		    self.sensor.AcceptADV_CH = True
		    self.NumHop = 1000
		    self.tcolor = "Green"
		    #node = self.sensor.node #Graphical sensor
		    self.sensor.setColour(api.SensorColor.FromName("Green")) #Change state

        else: #Sensing node
            self.sensor.timestamp = timeStamp()
            self.sensor.myCH = '-'
            self.lastNode = '-'
            self.NumHop = 1000
            self.Wtimestamp = timeStamp()
            self.sensor.points = {}
            self.sensor.AggTimeStamp = timeStamp()
            self.sensor.scheduleTime = timeStamp()
            self.sensor.AcceptADV_CH = True
            self.tcolor = "Green"


#Packet can be used for all kind of transmissions since receiver is optional
class APacket(api.Packet):
	def __init__(self, sender, rec, data):
		api.Packet.__init__(self, sender, rec, data)
		return

#Dan code
class timeStamp:
    def __init__(self, expires = 0):
        #Helper method to set timestamps on objects.
        added = (time.time() + expires)
        foo = time.localtime(added)
        self.hour = foo.tm_hour
        self.min =  foo.tm_min
        self.sec =  foo.tm_sec
            
    def __repr__(self):
        return "<time>(%i:%i:%i)" %(self.hour,self.min,self.sec)

    def diff(self,other):
        #Returns time Difference in Seconds in form OTHER - Given
        h = (other.hour- self.hour) *60 *60 
        m = (other.min - self.min) *60
        s = (other.sec - self.sec)
        return h+m+s

if __name__ == "__main__":
    print "Starting Simulation"
